#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <GL/freeglut_std.h>
#include "lc_array.h"
#include "pac_level.h"
#include "pac_object.h"
#include "pac_game.h"
#include "pac_logger.h"
#include "pac_values.h"
#include "pac_ghost.h"
#include "pac_logger.h"
#include "pac_sound_player.h"


PacGame* createGame(LCarray* aLevelList) {
   pac_log(LOG_INFO, "createGame\n");
        
    PacGame * pacGame = (PacGame*) lcMalloc(sizeof (PacGame));
    
    pacGame->levelsList = aLevelList;

    pacGame->currentLevel = lcArrayGet(aLevelList, 0);

    pacGame->currentLevelIndex = 0;

    pacGame->eatenGhostCount = 0;

    pacGame->playerScore = 0;

    pacGame->playerLifeCount = PACMAN_LIFE_COUNT;
    
    pacGame->gameIsPaused = FALSE;

    pacGame->gameWillAutoUnpause = FALSE;

    pacGame->gamePauseTime = 0;
    
    pacGame->gameIsWon = FALSE;

    pacGame->gameIsOver = FALSE;

    pacGame->gameIsStarted = FALSE;
    
    pacGame->isFPSMode = FALSE;
    
    pacGame->cameraAngle = 0;
    
    pacGame->cameraAngleDelta = 1;
    
    pacGame->cameraAngleTarget = 0;
    
    pacGame->endOfPowerSoundPlayed = TRUE;
           
    return pacGame;
}

void freePacGame(PacGame * aPacGame) {
    int i;
    for (i = 0; i < lcArrayLength(aPacGame->levelsList); i++) {
        levelFree(lcArrayGet(aPacGame->levelsList, i));
    }

    free(aPacGame);
}

void processWin(PacGame* aPacGame) {
    playWinSound();
    aPacGame->gameIsStarted = FALSE;
    aPacGame->gameIsWon = TRUE;
}

void processGameOver(PacGame* aPacGame) {
    playGameOverSound();
    aPacGame->gameIsStarted = FALSE;
    aPacGame->gameIsOver = TRUE;
}

void pauseGame(PacGame * aPacGame) {
     aPacGame->gamePauseTime = -1;
    aPacGame->gameWillAutoUnpause = FALSE;
    aPacGame->gameIsPaused = TRUE;
}

void unpauseGame(PacGame * aPacGame) {
    aPacGame->gameWillAutoUnpause = FALSE;
    aPacGame->gameIsPaused = FALSE;
}

void pauseGameDuring(PacGame * aPacGame, int aSecondCount) {
    pauseGame(aPacGame);
     aPacGame->gameWillAutoUnpause = TRUE;
     aPacGame->gamePauseTime = aSecondCount * 1000;
}

void restartLevel(PacGame* aPacGame) {
    pac_log(LOG_INFO, "restartLevel\n");
    
    aPacGame->cameraAngle = 0;
    aPacGame->cameraAngleTarget = 0;
    
    // Replacement de PacMan
    replaceObjectToInitialPosition(aPacGame->currentLevel->pacMan);

    // Replacement des fantômes
    int ghostListSize = lcArrayLength(aPacGame->currentLevel->ghosts);
    int i;
    PacGhost * currentGhost;

    // Parcours de la liste de tous les fantômes
    for (i = 0; i < ghostListSize; i++) {
        // Récupération du fantôme
        currentGhost = lcArrayGet(aPacGame->currentLevel->ghosts, i);
        
        // Tous les fantômes redeviennent actifs
        currentGhost->isGoingToHome = FALSE;
        currentGhost->isVulnerable = FALSE;
        
        // Replacement du fantôme à sa position initiale
        replaceGhostToInitialPosition(currentGhost);
        
        // Démarrage du déplacement du fantôme
        applyCaseChangementForGhost(aPacGame, currentGhost);
    }

    // Pause de 2 secondes avant la reprise
    pauseGameDuring(aPacGame, 2);
}

void initAllLevelPacBalls(PacGame * aPacGame) {
    pac_log(LOG_INFO, "initAllLevelPacBalls\n");

    int i;
    PacLevel * currentLevel;
    
    // Parcours de tous les niveaux
    for (i = 0; i < lcArrayLength(aPacGame->levelsList); i++) {
        currentLevel = lcArrayGet(aPacGame->levelsList, i);

        // Initialisation des PacBalls pour chacun des niveaux
        initPacBallsMap(currentLevel);
    }
}

void restartGame(PacGame * aPacGame) {

    // Remise à zéro des paramètre du jeu
    aPacGame->gameIsWon = FALSE;
    aPacGame->gameIsOver = FALSE;
    aPacGame->gameIsStarted = TRUE;
    aPacGame->eatenGhostCount = 0;
    aPacGame->playerScore = 0;
    aPacGame->playerLifeCount = PACMAN_LIFE_COUNT;
    aPacGame->currentLevelIndex = 0;
    aPacGame->currentLevel = lcArrayGet(aPacGame->levelsList, 0);

    // Remise à zéro des Map de PacBalls des niveaux
    initAllLevelPacBalls(aPacGame);
    
    // Démarrage du niveau
    restartLevel(aPacGame);

    // Mise en pause pour le départ
    pauseGameDuring(aPacGame, 5);

    // Lancement du son de départ
    playStartSound();
}

void swapToNextLevel(PacGame* aPacGame) {
    pac_log(LOG_INFO, "swapToNextLevel\n");

    // S'il ne reste plus de niveau
    if (aPacGame->currentLevelIndex == (lcArrayLength(aPacGame->levelsList) - 1)) {

        // Lancement de l'action correspondant à la victoire (fin du jeu!)
        processWin(aPacGame);
    }
    else {
        // Sinon, passage au niveau supérieur
        aPacGame->currentLevelIndex++;
        aPacGame->currentLevel = lcArrayGet(aPacGame->levelsList, aPacGame->currentLevelIndex);
        
        // Déamrrage du niveau
        restartLevel(aPacGame);
             
        // Pause de 4 secondes
        pauseGameDuring(aPacGame,4);
    }
}

void addValueToScore(PacGame* aPacGame, int aValue) {
    pac_log(LOG_DEBUG, "addValueToScore\n");
    // Récupération du modulo du score avec le score nécessaire pour une vie
    int lastScoreMod = aPacGame->playerScore % EXTRA_LIFE_SCORE;

    // Ajout de la valeur au score du joueur.
    aPacGame->playerScore += aValue;

    // Si le nouveau score dépasse la barre du score nécessaire pour une vie
    if (lastScoreMod > aPacGame->playerScore % EXTRA_LIFE_SCORE) {
        // Ajout d'une vie au joueur!
        aPacGame->playerLifeCount++;
        
        // Lancement du son de vie supplémentaire
        playExtraLifeSound();
    }
}


void processPacmanLostFight(PacGame* aPacGame) {
    pac_log(LOG_INFO, "processPacmanLostFight\n");
    
    // Perte d'une vie
    aPacGame->playerLifeCount--;
    
    playPacmanEatenSound();
    
    wait(1);
        
    // S'il ne reste plus de vie
    if (aPacGame->playerLifeCount < 0) {
        
        // Fin de la partie!
        processGameOver(aPacGame);
    } else {
        
        // Sinon, rédemarrage du niveau
        restartLevel(aPacGame);
    }
}

void processGhostLostFight(PacGame* aPacGame, PacGhost * aGhost) {
    pac_log(LOG_INFO, "processGhostLostFight\n");
    
    playGhostEatenSound();
    
    aPacGame->eatenGhostCount++;
    addValueToScore(aPacGame, aPacGame->eatenGhostCount * GHOST_POINT);

    aGhost->isGoingToHome = TRUE;
    aGhost->isVulnerable = FALSE;
    
    aGhost->targetPosX = aGhost->pacObject->initialPosX;
    aGhost->targetPosY = aGhost->pacObject->initialPosY;
    
}

void processPacFight(PacGame* aPacGame, PacObject * aPacMan, PacGhost * aGhost) {
    pac_log(LOG_INFO, "processPacFight\n");
    
    // Si le fantôme est vulnérable
    if (aGhost->isVulnerable) {
        // Le fantome perds le combat
        processGhostLostFight(aPacGame, aGhost);
    } 
    // Sinon, si le fantome n'est pas en train de retourner chez lui
    else if (!aGhost->isGoingToHome ){
        // PacMan perds le combat
        processPacmanLostFight(aPacGame);
    }

}

void checkIfLevelIsTerminated(PacGame* aPacGame) {
    pac_log(LOG_DEBUG, "checkIfLevelIsTerminated\n");

    // s'il ne reste plus de PacBalls
    if (aPacGame->currentLevel->remaining_pacBalls <= 0) {
        wait(1);
        swapToNextLevel(aPacGame);
    }
}

void processPacGumEating(PacGame* aPacGame, PacObject * aPacMan) {
    pac_log(LOG_DEBUG, "processPacGumEating\n");
    
    playPacGumEatenSound();
    
    addValueToScore(aPacGame, PAC_GUM_POINT);
    removePacBallAt(aPacGame->currentLevel, aPacMan->lastPosX, aPacMan->lastPosY);
    checkIfLevelIsTerminated(aPacGame);
}

void processPowerBallEating(PacGame* aPacGame, PacObject * aPacMan) {
    pac_log(LOG_DEBUG, "processPowerBallEating\n");
    
    // Lancement du son
    playPowerBallEatenSound();
    aPacGame->endOfPowerSoundPlayed = FALSE;
    
    aPacGame->eatenGhostCount = 0;
    aPacGame->powerBallPowerTime = aPacGame->currentLevel->powerball_duration;
    addValueToScore(aPacGame, POWER_BALL_POINT);
    removePacBallAt(aPacGame->currentLevel, aPacMan->lastPosX, aPacMan->lastPosY);
    checkIfLevelIsTerminated(aPacGame);
    

    // Activation de la vulnérabilité des fantômes
    int ghostListSize = lcArrayLength(aPacGame->currentLevel->ghosts);
    int i;
    PacGhost * currentGhost;

    // Parcours de la liste de tous les fantômes
    for (i = 0; i < ghostListSize; i++) {
        currentGhost = lcArrayGet(aPacGame->currentLevel->ghosts, i);

        // Si le fantome n'est pas en train de retourner chez lui
        if (!currentGhost->isGoingToHome) {
            // Il devient vulnérable
            currentGhost->isVulnerable = TRUE;
        }
    }
}

int pacManCanGoLeft(PacGame * aPacGame) {
    return levelCaseIsAccessibleForPacman(aPacGame->currentLevel, aPacGame->currentLevel->pacMan->lastPosX - 1, aPacGame->currentLevel->pacMan->lastPosY);
}

int pacManCanGoRight(PacGame * aPacGame) {
    return levelCaseIsAccessibleForPacman(aPacGame->currentLevel, aPacGame->currentLevel->pacMan->lastPosX + 1, aPacGame->currentLevel->pacMan->lastPosY);
}

int pacManCanGoUp(PacGame * aPacGame) {
    return levelCaseIsAccessibleForPacman(aPacGame->currentLevel, aPacGame->currentLevel->pacMan->lastPosX, aPacGame->currentLevel->pacMan->lastPosY + 1);
}

int pacManCanGoDown(PacGame * aPacGame) {
    return levelCaseIsAccessibleForPacman(aPacGame->currentLevel, aPacGame->currentLevel->pacMan->lastPosX, aPacGame->currentLevel->pacMan->lastPosY - 1);
}

int ghostCanGoLeft(PacGame * aPacGame, PacGhost * aGhost) {
    return levelCaseIsAccessibleForGhost(aPacGame->currentLevel, aGhost,aGhost->pacObject->posX - 1, aGhost->pacObject->posY);
}

int ghostCanGoRight(PacGame * aPacGame, PacGhost * aGhost) {
    return levelCaseIsAccessibleForGhost(aPacGame->currentLevel, aGhost,aGhost->pacObject->posX + 1, aGhost->pacObject->posY);
}

int ghostCanGoUp(PacGame * aPacGame, PacGhost * aGhost) {
    return levelCaseIsAccessibleForGhost(aPacGame->currentLevel,aGhost, aGhost->pacObject->posX, aGhost->pacObject->posY + 1);
}

int ghostCanGoDown(PacGame * aPacGame, PacGhost * aGhost) {
    return levelCaseIsAccessibleForGhost(aPacGame->currentLevel,aGhost,aGhost->pacObject->posX, aGhost->pacObject->posY - 1);
}



void checkPacManDeplacement(PacGame* aPacGame) {
    pac_log(LOG_DEBUG, "checkPacManDeplacement\n");

    PacObject * pacMan = aPacGame->currentLevel->pacMan;

    boolean pacmanCanKeepDirection = TRUE;

    // Vérification de la validité du déplacement courant
    switch (pacMan->currentMove) {
        case MOVE_UP:

            // Si la case du haut n'est pas accessible
            if (!pacManCanGoUp(aPacGame)) {
                pacmanCanKeepDirection = FALSE;
            }
            break;

        case MOVE_DOWN:
            // Si la case du bas n'est pas accessible
            if (!pacManCanGoDown(aPacGame)) {
                pacmanCanKeepDirection = FALSE;

            }
            break;

        case MOVE_LEFT:
            // Si la case de gauche n'est pas accessible
            if (!pacManCanGoLeft(aPacGame)) {
                pacmanCanKeepDirection = FALSE;

            }
            break;

        case MOVE_RIGHT:
            // Si la case de droite n'est pas accessible
            if (!pacManCanGoRight(aPacGame)) {
                pacmanCanKeepDirection = FALSE;
            }
            break;

        default:
            // Do nothing.
            break;

    }

    // Si PacMan ne peux pas garder sa direction 
    if (!pacmanCanKeepDirection) {
        pac_log(LOG_DEBUG, "PacMan CAN'T move next!\n");
        
        // Desactivation des déplacements
        pacMan->lastMove = pacMan->currentMove;
        pacMan->currentMove = NO_MOVE;
        pacMan->nextMove = NO_MOVE;
    } else {
        pac_log(LOG_DEBUG, "PacMan CAN move next!\n");
    }


}

void checkPacBallForCurrentCase(PacGame* aPacGame, PacObject * aPacMan){
pac_log(LOG_DEBUG, "checkPacBallForCurrentCase\n");

    // Récupération de la PacBall à la position de PacMan
    int pacBallCode = levelGetPacBallAt(aPacGame->currentLevel, aPacMan->lastPosX, aPacMan->lastPosY);

    // Verification de la présence d'une PacBall
    switch (pacBallCode) {
        
            // Si c'est une PacGum
        case PAC_GUM_CODE:
            processPacGumEating(aPacGame, aPacMan);
            break;

            // Si c'est une PowerBall
        case POWER_BALL_CODE:
            processPowerBallEating(aPacGame, aPacMan);
            break;

        default:
            // Do nothing...
            break;
    }
}

void doTeleportation(PacGame* aPacGame, PacObject * aPacObject) {
            pac_log(LOG_DEBUG, "doTeleportation\n");

    // Récupération de la cible de la téléportation
    int * teleportationTarget = levelGetTeleporterTarget(aPacGame->currentLevel, aPacObject->posX, aPacObject->posY);

    // Si la cible n'a pas été trouvée
    if (teleportationTarget[0] == aPacObject->posX && teleportationTarget[1] == aPacObject->posY) {

        // L'objet est imobilisé
        aPacObject->currentMove = NO_MOVE;
        aPacObject->nextMove = NO_MOVE;
    } else {
        // Sinon, l'objet est téléporté
        aPacObject->posX = teleportationTarget[0];
        aPacObject->posY = teleportationTarget[1];
        aPacObject->lastPosX = aPacObject->posX;
        aPacObject->lastPosY = aPacObject->posY;
        
       playVortexSound();
    }
}

void applyChangeDirectionForCamera(PacGame* aPacGame, PacObject * aPacMan){
        pac_log(LOG_DEBUG, "applyChangeDirectionForCamera\n");

    int pacManDirection = aPacMan->currentMove; 
    
    // Si PacMan ne bouge plus
    if (pacManDirection == NO_MOVE) {

        // Récupération du dernier déplacement
        pacManDirection = aPacMan->lastMove;
    }
    
    switch (pacManDirection) {

        case MOVE_UP:
            switch (aPacMan->nextMove) {
                case MOVE_RIGHT:
                    aPacGame->cameraAngleDelta = 1;
                    aPacGame->cameraAngleTarget += 90;
                    break;

                case MOVE_LEFT:
                    aPacGame->cameraAngleDelta = -1;
                    aPacGame->cameraAngleTarget -= 90;
                    break;
                    
                case MOVE_DOWN :
                     aPacGame->cameraAngleDelta = 1;
                    aPacGame->cameraAngleTarget += 180;
                    break;
            }
            
            break;

        case MOVE_DOWN:
            switch (aPacMan->nextMove) {
                case MOVE_RIGHT:
                    aPacGame->cameraAngleDelta = -1;
                    aPacGame->cameraAngleTarget -= 90;
                    break;

                case MOVE_LEFT:
                    aPacGame->cameraAngleDelta = +1;
                    aPacGame->cameraAngleTarget += 90;
                    break;
                    
               case MOVE_UP :
                     aPacGame->cameraAngleDelta = -1;
                    aPacGame->cameraAngleTarget -= 180;
                    break;
            }
            break;

        case MOVE_LEFT:
            switch (aPacMan->nextMove) {
                case MOVE_UP:
                    aPacGame->cameraAngleDelta = +1;
                    aPacGame->cameraAngleTarget += 90;
                    break;

                case MOVE_DOWN:
                    aPacGame->cameraAngleDelta = -1;
                    aPacGame->cameraAngleTarget -= 90;
                    break;
                                  
                case MOVE_RIGHT :
                     aPacGame->cameraAngleDelta = 1;
                    aPacGame->cameraAngleTarget += 180;
                    break;
            }
            break;

        case MOVE_RIGHT:
            switch (aPacMan->nextMove) {
                case MOVE_UP:
                    aPacGame->cameraAngleDelta = -1;
                    aPacGame->cameraAngleTarget -= 90;
                    break;

                case MOVE_DOWN:
                    aPacGame->cameraAngleDelta = +1;
                    aPacGame->cameraAngleTarget += 90;
                    break;
                    
               case MOVE_LEFT :
                    aPacGame->cameraAngleDelta = -1;
                    aPacGame->cameraAngleTarget -= 180;
                    break;
            }
            break;
            
    }
    
}

void applyCaseChangementForPacman(PacGame* aPacGame, PacObject * aPacMan) {
    pac_log(LOG_DEBUG, "applyCaseChangementForPacman\n");
    PacObject * pacMan = aPacGame->currentLevel->pacMan;

    // Vérification de la présence d'une PacBall sur la case
    checkPacBallForCurrentCase(aPacGame, aPacMan);
    
    // Si la case est un téléporteur
    if (levelGetCaseAt(aPacGame->currentLevel, aPacMan->posX, aPacMan->posY) <0){
        // Execution de la téléportation
        doTeleportation(aPacGame, aPacMan);
    }
    
    int pacmanCanChangeDirection = TRUE;

    // Vérification de la possibilité de changer de direction.
    switch (pacMan->nextMove) {
        case MOVE_UP:

            // Si la case du haut n'est pas accessible
            if (!pacManCanGoUp(aPacGame)) {
                pacmanCanChangeDirection = FALSE;
            }
            break;

        case MOVE_DOWN:
            // Si la case du bas n'est pas accessible
            if (!pacManCanGoDown(aPacGame)) {
                pacmanCanChangeDirection = FALSE;
            }
            break;

        case MOVE_LEFT:
            // Si la case de gauche n'est pas accessible
            if (!pacManCanGoLeft(aPacGame)) {
                pacmanCanChangeDirection = FALSE;
            }
            break;

        case MOVE_RIGHT:
            // Si la case de droite n'est pas accessible
            if (!pacManCanGoRight(aPacGame)) {
                pacmanCanChangeDirection = FALSE;
            }
            break;

        default:
            // Do nothing.
            break;

    }
    
    // Si pacMan ne bouge pas, il peux changer de direction
    if (pacMan->currentMove == NO_MOVE){
        pacmanCanChangeDirection = TRUE;
    }

    // Si PacMan peux changer de direction
    if (pacMan->nextMove != NO_MOVE && pacmanCanChangeDirection) {
        pac_log(LOG_DEBUG, "PacMan CAN change direction\n");
        
        // Changement de la caméra
        applyChangeDirectionForCamera(aPacGame, aPacMan);
        
        // Changement du déplacement
        pacMan->lastMove = pacMan->currentMove;
        pacMan->currentMove = pacMan->nextMove;
        pacMan->nextMove = NO_MOVE;
        
    } else {
        // Sinon, on vérifie que le déplacement courant est toujours valide
        checkPacManDeplacement(aPacGame);
    }

}

boolean ghostWillGoLeft(PacGame* aPacGame, PacGhost * aGhost) {
    boolean willGoLeft = FALSE;
    // Si le fantôme peut aller sur la case de gauche
    if (ghostCanGoLeft(aPacGame, aGhost)) {

        // Si le fantome se déplaçait vers la droite
        if (aGhost->pacObject->currentMove == MOVE_RIGHT) {

            // Autorisation de demi-tour uniquement si les autres ne sont pas possibles
            if (!ghostCanGoRight(aPacGame, aGhost) && !ghostCanGoUp(aPacGame, aGhost) && !ghostCanGoDown(aPacGame, aGhost)) {
                aGhost->pacObject->currentMove = MOVE_LEFT;
                willGoLeft = TRUE;
            }
        } else {
            // Sinon, changement direct
            aGhost->pacObject->currentMove = MOVE_LEFT;
            willGoLeft = TRUE;
        }
        
        // Réalignement éventuel au centre de la case.
      if (willGoLeft){
          // aGhost->pacObject->posY = (int)aGhost->pacObject->posY ;
        }
    }
    return willGoLeft;
}

boolean ghostWillGoRight(PacGame* aPacGame, PacGhost * aGhost) {
    boolean willGoRight = FALSE;
    // Si le fantôme peut aller sur la case de droite
    if (ghostCanGoRight(aPacGame, aGhost)) {

        // Si le fantome se déplaçait vers la gauche
        if (aGhost->pacObject->currentMove == MOVE_LEFT) {

            // Autorisation de demi-tour uniquement si les autres ne sont pas possibles
            if (!ghostCanGoLeft(aPacGame, aGhost) && !ghostCanGoUp(aPacGame, aGhost) && !ghostCanGoDown(aPacGame, aGhost)) {
                aGhost->pacObject->currentMove = MOVE_RIGHT;
                willGoRight = TRUE;
            }
        } else {
            // Sinon, changement direct
            aGhost->pacObject->currentMove = MOVE_RIGHT;
            willGoRight = TRUE;
        }
        
        // Réalignement éventuel au centre de la case.
        if (willGoRight){
           // aGhost->pacObject->posY = (int)aGhost->pacObject->posY ;
        }
    }
    return willGoRight;
}

boolean ghostWillGoUp(PacGame* aPacGame, PacGhost * aGhost) {
    boolean willGoUp = FALSE;

    // Si le fantôme peut aller sur la case du haut
    if (ghostCanGoUp(aPacGame, aGhost)) {

        // Si le fantome se déplaçait vers le haut
        if (aGhost->pacObject->currentMove == MOVE_DOWN) {

            // Autorisation de demi-tour uniquement si les autres ne sont pas possibles
            if (!ghostCanGoDown(aPacGame, aGhost) && !ghostCanGoLeft(aPacGame, aGhost) && !ghostCanGoRight(aPacGame, aGhost)) {
                aGhost->pacObject->currentMove = MOVE_UP;
                willGoUp = TRUE;
            }
        } else {
            // Sinon, changement direct
            aGhost->pacObject->currentMove = MOVE_UP;
            willGoUp = TRUE;
        }
        
        // Réalignement éventuel au centre de la case.
                if (willGoUp){
            //aGhost->pacObject->posX = (int)aGhost->pacObject->posX ;
        }
        
    }

    return willGoUp;
}

boolean ghostWillGoDown(PacGame* aPacGame, PacGhost * aGhost) {
    boolean willGoDown = FALSE;
    // Si le fantôme peut aller sur la case du bas
    if (ghostCanGoDown(aPacGame, aGhost)) {

        // Si le fantome se déplaçait vers le haut
        if (aGhost->pacObject->currentMove == MOVE_UP) {

            // Autorisation de demi-tour uniquement si les autres ne sont pas possibles
            if (!ghostCanGoUp(aPacGame, aGhost) && !ghostCanGoLeft(aPacGame, aGhost) && !ghostCanGoRight(aPacGame, aGhost)) {
                aGhost->pacObject->currentMove = MOVE_DOWN;
                willGoDown = TRUE;
            }
        } else {
            // Sinon, changement direct
            aGhost->pacObject->currentMove = MOVE_DOWN;
            willGoDown = TRUE;
        }
        // Réalignement éventuel au centre de la case.
                        if (willGoDown){
            //aGhost->pacObject->posX = (int)aGhost->pacObject->posX ;
        }
    }
    return willGoDown;
}

void moveGhostToRandomDirection(PacGame* aPacGame, PacGhost * aGhost) {

    pac_log(LOG_DEBUG, "moveGhostToRandomDirection\n");

    boolean directionChanged = FALSE;

    while (!directionChanged) {

        int randomInt = getRandomIntUnder(4);

        switch (randomInt) {
                // Le déplacement vers le haut a été tiré!
            case 0:
                directionChanged = ghostWillGoUp(aPacGame, aGhost);
                break;

                // Le déplacement vers le bas a été tiré!
            case 1:
                directionChanged = ghostWillGoDown(aPacGame, aGhost);
                break;

                // Le déplacement vers la gauche a été tiré!
            case 2:
                directionChanged = ghostWillGoLeft(aPacGame, aGhost);
                break;

                // Le déplacement vers la droite a été tiré!
            case 3:
                directionChanged = ghostWillGoRight(aPacGame, aGhost);
                break;
        }
    }
}

void moveGhostToTargetDirection(PacGame* aPacGame, PacGhost * aGhost) {

    boolean directionChanged = FALSE;


    // Si la cible est plus basse que le fantôme
    if (aGhost->targetPosY < aGhost->pacObject->posY) {

        // Demande de changement de direction vers le bas
        directionChanged = ghostWillGoDown(aPacGame, aGhost);
    }

    // Si la direction n'a pas changée et que la cible est plus haute que le fantôme
    if (!directionChanged && aGhost->targetPosY > aGhost->pacObject->posY) {

        // Demande de changement de direction vers le haut
        directionChanged = ghostWillGoUp(aPacGame, aGhost);
    }


    // Si la direction n'a pas changée et que la cible est à gauche du fantôme
    if (!directionChanged && aGhost->targetPosX < aGhost->pacObject->posX) {

        // Demande de changement de direction vers le haut
        directionChanged = ghostWillGoLeft(aPacGame, aGhost);
    }

    // Si la direction n'a pas changée et que la cible est à droite du fantôme
    if (!directionChanged && aGhost->targetPosX > aGhost->pacObject->posX) {

        // Demande de changement de direction vers le haut
        directionChanged = ghostWillGoRight(aPacGame, aGhost);
    }
    

    // Si la position n'a toujours pas été changée
    if (!directionChanged) {
        moveGhostToRandomDirection(aPacGame, aGhost);
    }
}



void applyCaseChangementForGhost(PacGame* aPacGame, PacGhost * aGhost) {
       pac_log(LOG_DEBUG, "applyCaseChangementForGhost\n");
       
        // Si la case est un téléporteur
    if (levelGetCaseAt(aPacGame->currentLevel, aGhost->pacObject->posX, aGhost->pacObject->posY) <0){
        // Execution de la téléportation
        doTeleportation(aPacGame, aGhost->pacObject);
    }
    

    // Si le fantôme doit retourner chez lui
    if (aGhost->isGoingToHome) {

        // Si le fantômes est arrivé
        if (aGhost->pacObject->posX == aGhost->pacObject->initialPosX && aGhost->pacObject->posY == aGhost->pacObject->initialPosY) {
            // Mise à jour de son statut
            aGhost->isGoingToHome = FALSE;

            // Déplacement aléatoire
            moveGhostToRandomDirection(aPacGame, aGhost);
        } else {
            // Mise à jour de la cible
            aGhost->targetPosX = aGhost->pacObject->initialPosX;
            aGhost->targetPosY = aGhost->pacObject->initialPosY;

            // Déplacement vers sa case de départ
            moveGhostToTargetDirection(aPacGame, aGhost);
            }
    }
    // Sinon, si le fantôme est vulnérable ou stupide
    else if (aGhost->isVulnerable || !aGhost->isIntelligent){
        // Déplacement aléatoire
        moveGhostToRandomDirection(aPacGame, aGhost);
    }
        // Sinon (si le fantôme est intelligent et en chasse)
    else {

        // Récupération  de l'intelligence du fanôme
        int ghostIntelligence = aPacGame->currentLevel->ghostIntelligence;

        // Récupération d'un chiffre en 1 et 5
        int randomValue = getRandomIntUnder(5) + 1;

        // Si la valeur de l'intelligence du fantôme est supérieur au nombre tiré
        // Inteligence = 0 => jamais // Intelligence = 5 => toujours
        if (ghostIntelligence >= randomValue) {

            // Mise à jour de la cible (PacMan)
            aGhost->targetPosX = aPacGame->currentLevel->pacMan->posX;
            aGhost->targetPosY = aPacGame->currentLevel->pacMan->posY;

            // Déplacement vers PacMac
            moveGhostToTargetDirection(aPacGame, aGhost);
        }            // Sinon, déplacement aléatoire du fantôme
        else {
            moveGhostToRandomDirection(aPacGame, aGhost);
        }
    }
}

int shouldProcessFightBetween(PacObject * aPacMan, PacGhost * aGhost) {
    int shouldProcessFight = FALSE;

    // Si les deux objets sont sur la même ligne (pour éviter des calculs inutiles)
    if (aGhost->pacObject->posX == aPacMan->posX || aGhost->pacObject->posY == aPacMan->posY) {

        // Calcul de la distance séparant les deux objets (d² = (x'-x)² + (y'-y)²)
        float dist2 = ((aGhost->pacObject->posX - aPacMan->posX)*(aGhost->pacObject->posX - aPacMan->posX))
                + ((aGhost->pacObject->posY - aPacMan->posY)*(aGhost->pacObject->posY - aPacMan->posY));

        float dist = sqrt(dist2);

        // Si la distance est plus petite que le rayon des deux objets
        if (dist <= (0.5)) {
            // Alors un combat doit avoir lieu
            shouldProcessFight = TRUE;
        }
    }

    return shouldProcessFight;
}

boolean checkPacFight(PacGame * aPacGame) {

    int ghostListSize = lcArrayLength(aPacGame->currentLevel->ghosts);
    int i;
    PacGhost * currentGhost;
    boolean pacFightOccured = FALSE;

    // Parcours de la liste de tous les fantômes
    for (i = 0; i < ghostListSize; i++) {
        currentGhost = lcArrayGet(aPacGame->currentLevel->ghosts, i);

        // Si le fantôme n'est pas en train de rentrer chez lui
        if (!currentGhost->isGoingToHome) {

            // Si un combat doit avoir lieu
            if (shouldProcessFightBetween(aPacGame->currentLevel->pacMan, currentGhost)) {

                // Lancement du combat
                processPacFight(aPacGame, aPacGame->currentLevel->pacMan, currentGhost);

                // Sortie de la boucle
                pacFightOccured = TRUE;
                break;
            }
        }
    }
    
    return pacFightOccured;
}
    

void movePacMan(PacGame* aPacGame, PacObject * aPacMan) {
    pac_log(LOG_DEBUG, "movePacMan\n");

    float depValue = 0.01;
    
    // Déplacement effectif de PacMan
    switch (aPacMan->currentMove) {

        case MOVE_UP:
            aPacMan->posY += depValue;
            aPacMan->currentDeplacementValue += depValue;
            break;

        case MOVE_DOWN:
            aPacMan->posY -= depValue;
            aPacMan->currentDeplacementValue -= depValue;
            break;

        case MOVE_LEFT:
            aPacMan->posX -= depValue;
            aPacMan->currentDeplacementValue -= depValue;
            break;

        case MOVE_RIGHT:
            aPacMan->posX +=  depValue;
            aPacMan->currentDeplacementValue += depValue;
            break;

        default:
            // Do nothing...
            break;
    }

    // Verification de si PacMan à changé de case.
    if (aPacMan->currentDeplacementValue <= -0.98 || aPacMan->currentDeplacementValue >= 1) {
        aPacMan->currentDeplacementValue = 0;

        // Récupération de la case courrante de PacMan
        aPacMan->lastPosX = (int) aPacMan->posX;
        aPacMan->lastPosY = (int) aPacMan->posY;
        
        // Réalignement éventuel de la position au centre de la case
        aPacMan->posX = (int) aPacMan->posX;
        aPacMan->posY = (int) aPacMan->posY;

        applyCaseChangementForPacman(aPacGame, aPacMan);
    }
}


void movePacManLoop(PacGame* aPacGame) {
    pac_log(LOG_DEBUG, "movePacManLoop\n");

    PacObject * pacMan = aPacGame->currentLevel->pacMan;
    int depValue = aPacGame->currentLevel->pacman_speed;
    
    // Si le jeux est en mode fps, les vitesses sont divisée par 3
    if (aPacGame->isFPSMode){
        depValue = depValue / 3;
    }
    
    int i;

    // Déplacement de pacman selon sa vitesse
    for (i = 0; i < depValue; i++) {
        
        // Déplacement effectif de pacman
        movePacMan(aPacGame, pacMan);

        // Vérification d'un éventuel combat entre pacman et les fantomes
        if (checkPacFight(aPacGame)) {
            // Sortie de la boucle si un combat a eu lieu.
            break;
        }
    }
    
}

void updateVulnerability(PacGame* aPacGame, PacGhost * aGhost) {
    // Si le fantôme est vulnérable
    if (aGhost->isVulnerable) {

        // Si la durée des pouvoir est écoulée
        if (aPacGame->powerBallPowerTime <= 0) {

            // Le fantôme n'est plus vulnérable
            aGhost->isVulnerable = FALSE;
        }
    }
}

void moveGhost(PacGame* aPacGame, PacGhost * aGhost) {
    pac_log(LOG_DEBUG, "moveGhost\n");

    // Vérification de la vulnérabilité
    updateVulnerability(aPacGame, aGhost);
    
    // Facteur de déplacement pour un mouvement
    float depValue = 0.01;
    
    // Déplacement effectif du fantôme
    switch (aGhost->pacObject->currentMove) {

        case MOVE_UP:
            aGhost->pacObject->posY += depValue;
            aGhost->pacObject->currentDeplacementValue += depValue;
            break;

        case MOVE_DOWN:
            aGhost->pacObject->posY -= depValue;
            aGhost->pacObject->currentDeplacementValue -= depValue;
            break;

        case MOVE_LEFT:
            aGhost->pacObject->posX -= depValue;
            aGhost->pacObject->currentDeplacementValue -= depValue;
            break;

        case MOVE_RIGHT:
            aGhost->pacObject->posX += depValue;
            aGhost->pacObject->currentDeplacementValue += depValue;
            break;

        default:
            // Do nothing...
            break;
    }
    
    // Verification de si le fantôme à changé de case.
    if (aGhost->pacObject->currentDeplacementValue <= -0.98 || aGhost->pacObject->currentDeplacementValue >= 1.0) {

        
        aGhost->pacObject->currentDeplacementValue = 0;
        
        // Récupération de la case courrante du fantôme
        aGhost->pacObject->lastPosX = (int) aGhost->pacObject->posX;
        aGhost->pacObject->lastPosY = (int) aGhost->pacObject->posY;
        
        // Réalignement éventuel de la position au centre de la case
        aGhost->pacObject->posX = (int) aGhost->pacObject->posX;
        aGhost->pacObject->posY = (int) aGhost->pacObject->posY;

        applyCaseChangementForGhost(aPacGame, aGhost);
    }
}

void moveGhostsLoop(PacGame* aPacGame) {
    pac_log(LOG_DEBUG, "moveGhosts\n");

    // Récupération du nombre de fantômes
    int size = lcArrayLength(aPacGame->currentLevel->ghosts);

    PacGhost* currentGhost;
    int x;

    // Parcours de la liste des fantômes
    for (x = 0; x < size; x++) {

        // Récupération du fantôme courant
        currentGhost = lcArrayGet(aPacGame->currentLevel->ghosts, x);

        // Récupération de la vitesse de déplacement des fantômes
        int depValue = aPacGame->currentLevel->ghost_normal_speed;

        // Si le fantôme est vulnérable
        if (currentGhost->isVulnerable) {

            // Modification de sa vitesse
            depValue = aPacGame->currentLevel->ghost_vulnerable_speed;
        }

        // Si le jeux est en mode fps, les vitesses sont divisée par 3
        if (aPacGame->isFPSMode) {
            depValue = depValue / 3;
        }

        int i;

        // Lancement des déplacements de fantôme
        for (i = 0; i < depValue; i++) {
            moveGhost(aPacGame, currentGhost);
        }
    }
}

void updateCameraAngle(PacGame * aPacGame){
    
    // Si l'angle doit être mis à jour
    if (aPacGame->cameraAngle != aPacGame->cameraAngleTarget){
        
        // Mise à jour de l'angle
        aPacGame->cameraAngle += 10 * aPacGame->cameraAngleDelta;
    }
    else{
        // Réalignement des valerus d'angles
        aPacGame->cameraAngle = aPacGame->cameraAngle % 360;
       aPacGame->cameraAngleTarget = aPacGame->cameraAngleTarget % 360;
    }
    
}


void changePacmanDirectionForNormalMode(PacGame* aPacGame, int aDirection) {
    pac_log(LOG_INFO, "changePacmanDirectionForNormalMode\n");
    PacObject * pacMan = aPacGame->currentLevel->pacMan;
    
    boolean changeDirectionNow = FALSE;

    
    int pacManDirection = pacMan->currentMove; 
    
    // Si PacMan ne bouge plus
    if (pacManDirection == NO_MOVE) {

        // Récupération du dernier déplacement
        pacManDirection = pacMan->lastMove;
    }
    
    switch (aDirection) {
            // Demande de déplacement vers le haut
        case MOVE_UP:

            // Si le déplacement était vers le bas ou s'il n'y avait pas de déplacement
            if (pacManDirection == MOVE_DOWN || pacMan->currentMove == NO_MOVE) {

                if (pacManCanGoUp(aPacGame)) {
                    // Changement direct de direction
                    pacMan->nextMove = MOVE_UP;
                    changeDirectionNow = TRUE;
                }

                }// Sinon, enregistrement de la demande de changement
            else {
                pacMan->nextMove = MOVE_UP;
            }
            break;

            // Demande de déplacement vers le bas
        case MOVE_DOWN:

            // Si le déplacement était vers le haut ou s'il n'y avait pas de déplacement
            if (pacManDirection == MOVE_UP || pacMan->currentMove == NO_MOVE) {

                if (pacManCanGoDown(aPacGame)) {
                    // Changement direct de direction
                    pacMan->nextMove = MOVE_DOWN;
                    changeDirectionNow = TRUE;
                    }
 
            }// Sinon, enregistrement de la demande de changement
            else {
                pacMan->nextMove = MOVE_DOWN;
            }
            break;

            // Demande de déplacement vers la gauche
        case MOVE_LEFT:

            // Si le déplacement était vers le droite ou s'il n'y avait pas de déplacement
            if (pacManDirection == MOVE_RIGHT || pacMan->currentMove == NO_MOVE) {
                
                if (pacManCanGoLeft(aPacGame)) {
                    // Changement direct de direction
                    pacMan->nextMove = MOVE_LEFT;
                    changeDirectionNow = TRUE;
                }
                
            }// Sinon, enregistrement de la demande de changement
            else {
                pacMan->nextMove = MOVE_LEFT;
            }
            break;

            // Demande de déplacement vers la droite
        case MOVE_RIGHT:

            // Si le déplacement était vers la gauche ou s'il n'y avait pas de déplacement
            if (pacManDirection == MOVE_LEFT || pacMan->currentMove == NO_MOVE) {

                if (pacManCanGoRight(aPacGame)) {
                    // Changement direct de direction
                    pacMan->nextMove = MOVE_RIGHT;
                    changeDirectionNow = TRUE;
                }
            }// Sinon, enregistrement de la demande de changement
            else {

                pacMan->nextMove = MOVE_RIGHT;
            }
            break;

            // Dans les autres cas
        default:
            // Do nothing.
            break;
    }

    if (changeDirectionNow || pacMan->currentMove == NO_MOVE) {
        // Si la direction ne peux pas changer
        if (pacMan->nextMove == NO_MOVE) {

            // Forçage du changement de la caméra
            pacMan->nextMove = aDirection;

            applyChangeDirectionForCamera(aPacGame, pacMan);
            pacMan->lastMove = aDirection;
            pacMan->nextMove = NO_MOVE;
        }

        applyCaseChangementForPacman(aPacGame, pacMan);
    }
}

void changePacmanDirectionForFPSMode(PacGame* aPacGame, int aDirection) {
        pac_log(LOG_INFO, "changePacmanDirectionForFPSMode\n");
        
    PacObject * pacMan = aPacGame->currentLevel->pacMan;

    // Récupération du déplacement de PacMan
    int pacManDirection = pacMan->currentMove;

    // Prochain déplacement
    int nextDirection = NO_MOVE;

    // Si PacMan ne bouge plus
    if (pacManDirection == NO_MOVE) {

        // Récupération du dernier déplacement
        pacManDirection = pacMan->lastMove;
    }

    // S'il n'y a pas eu de déplacement
    if (pacManDirection == NO_MOVE) {

        // Le déplacement n'est pas transformé et est testé directement.
        nextDirection = aDirection;
    } else {

        // Sinon, changement de direction calculé par rapport à la derniere direction
        switch (aDirection) {
                // Demande de déplacement vers le haut
            case MOVE_UP:
                // Pas de changement de direction
                break;

                // Demande de déplacement vers le bas
            case MOVE_DOWN:

                // Le déplacement demandé est relatif à la direction courante
                switch (pacManDirection) {
                        // Le déplacement courant est vers le haut
                    case MOVE_UP:
                        // Demande de déplacement vers le bas
                        nextDirection = MOVE_DOWN;
                        break;

                        // Le déplacement courant est vers le bas
                    case MOVE_DOWN:
                        // Demande de déplacement vers le haut
                        nextDirection = MOVE_UP;
                        break;

                        // Le déplacement courant est vers la gauche
                    case MOVE_LEFT:
                        // Demande de déplacement vers la droite
                        nextDirection = MOVE_RIGHT;
                        break;

                        // Le déplacement courant est vers la droite
                    case MOVE_RIGHT:
                        // Demande de déplacement vers la gauche
                        nextDirection = MOVE_LEFT;
                        break;

                        // Dans les autres cas
                    default:
                        // Do nothing.
                        break;
                }
                break;

                // Demande de déplacement vers la gauche
            case MOVE_LEFT:
                // Le déplacement demandé est relatif à la direction courante
                switch (pacManDirection) {
                        // Le déplacement courant est vers le haut
                    case MOVE_UP:
                        // Demande de déplacement vers la gauche
                        nextDirection = MOVE_LEFT;

                        break;

                        // Le déplacement courant est vers le bas
                    case MOVE_DOWN:
                        // Demande de déplacement vers la droite
                        nextDirection = MOVE_RIGHT;
                        break;

                        // Le déplacement courant est vers la gauche
                    case MOVE_LEFT:
                        // Demande de déplacement vers le bas
                        nextDirection = MOVE_DOWN;
                        break;

                        // Le déplacement courant est vers la droite
                    case MOVE_RIGHT:
                        // Demande de déplacement vers le haut
                        nextDirection = MOVE_UP;
                        break;

                        // Dans les autres cas
                    default:
                        // Do nothing.
                        break;
                }
                break;

                // Demande de déplacement vers la droite
            case MOVE_RIGHT:

                // Le déplacement demandé est relatif à la direction courante
                switch (pacManDirection) {
                        // Le déplacement courant est vers le haut
                    case MOVE_UP:
                        // Demande de déplacement vers la droite
                        nextDirection = MOVE_RIGHT;
                        break;

                        // Le déplacement courant est vers le bas
                    case MOVE_DOWN:
                        // Demande de déplacement vers la gauche
                        nextDirection = MOVE_LEFT;
                        break;

                        // Le déplacement courant est vers la gauche
                    case MOVE_LEFT:
                        // Demande de déplacement vers le bas
                        nextDirection = MOVE_UP;
                        break;

                        // Le déplacement courant est vers la droite
                    case MOVE_RIGHT:
                        // Demande de déplacement vers le bas
                        nextDirection = MOVE_DOWN;
                        break;

                        // Dans les autres cas
                    default:
                        // Do nothing.
                        break;
                }
                break;

                // Dans les autres cas
            default:
                // Do nothing...
                break;
        }
    }

    // Si la direction doit changer
    if (nextDirection != NO_MOVE) {
        changePacmanDirectionForNormalMode(aPacGame, nextDirection);
        pacMan->lastMove = nextDirection;
    }

}

void changePacmanDirection(PacGame* aPacGame, int aDirection) {
    pac_log(LOG_INFO, "changePacmanDirection\n");

    // Si le jeu est en mode FPS
    if (aPacGame->isFPSMode) {

        // Changement de direction spécifique (par rapport à la direction courante)
        changePacmanDirectionForFPSMode(aPacGame, aDirection);
    } else {
        // Changement de direction normal (par rapport à la vue générale)
        changePacmanDirectionForNormalMode(aPacGame, aDirection);
    }

}

void updatePauseStatus(PacGame * aPacGame) {

    // Si le jeu est en pause
    if (aPacGame->gameIsPaused) {

        // Dérémentation du temps de pause restant.
        aPacGame->gamePauseTime -= INTER_STEP_TIME;

        // Si la pause est une pause temporaire
        if (aPacGame->gameWillAutoUnpause) {

            // Si le temps de pause est écoulé
            if (aPacGame->gamePauseTime <= 0) {

                // Le mode pause est désactivé
                unpauseGame(aPacGame);
            }
        }
    }
}

void updatePowerDuration(PacGame * aPacGame) {
    if (aPacGame->powerBallPowerTime > 0) {
        aPacGame->powerBallPowerTime -= INTER_STEP_TIME;

        // S'il reste moins d'une seconde et que le son n'a pas été joué
        if (aPacGame->powerBallPowerTime <= 1000 && !aPacGame->endOfPowerSoundPlayed) {
            aPacGame->endOfPowerSoundPlayed = TRUE;
            playEndOfPowerSound();
        }
    }
}

void processGameStep(PacGame * aPacGame) {
    pac_log(LOG_DEBUG, "processGameStep\n");

    // Mise à jour du statut 'Pause'
    updatePauseStatus(aPacGame);

    // Mise à jour de la caméra
    updateCameraAngle(aPacGame);

    // Si le jeu n'est pas en mode 'Pause'
    if (!aPacGame->gameIsPaused) {

        // Mise à jour de la durée de pouvoir
        updatePowerDuration(aPacGame);

        // Déplacement des fantômes
        moveGhostsLoop(aPacGame);

        // Déplacement de PacMan
        movePacManLoop(aPacGame);
    }


    glutPostRedisplay();
}




